import { h, Schema } from "koishi"

export const name = "imagify"

export const inject = {
  required: ["puppeteer"],
  optional: ["lnnbotSideloadFonts"],
}

export const Config = Schema.object({
  fallbackFonts: Schema.string().default("sans-serif, 'Noto Color Emoji'"),
  extraCSS: Schema.string().default(""),
})

function isShorthandFontProp(cssValue) {
  return /^\s*(?:(?:normal|italic|oblique(?:\s+(?:\d*\.)?\d+(?:[Ee][+-]?\d+)?(?:deg|grad|rad|turn))?|small-caps|(?:ultra-|extra-|semi-)?(?:condensed|expanded)|bold|lighter|bolder|(?:\d*\.)?\d+(?:[Ee][+-]?\d+)?)\s+)*(?:(?:[x]{1,2}-)?(?:large|small)|medium|smaller|larger|(?:\d*\.)?\d+(?:[Ee][+-]?\d+)?(?:px|cm|mm|Q|in|pc|pt|%|vh|vw|vi|vb|vmin|max|cap|ch|em|ex|ic|lh|rem|rlh))(?=[\/\s])/.test(cssValue)
}

export async function apply(ctx, config) {
  ctx
    .command("imagify <content:el>", { checkUnknown: true })
    .option("html", "-H, -E")
    .option("font", "-f <css-font>", { fallback: "1rem 'Sarasa Term SC'" })
    .option("width", "-w <css-length>", { fallback: "100rem" })
    .option("width", "-W, --no-width", { value: false })
    .option("padding", "-p <css-padding>", { fallback: "0.25rem" })
    .option("padding", "-P, --no-padding", { value: "0" })
    .option("lang", "-l <code>")
    .option("rest", "-- <content:el>", { descPath: "lnnbot.general.rest-option" })
    .action(async ({ session, options }, content) => {
      content ??= options.rest
      if (!content?.join("").trim()) return session.text(".empty")
      const styles = ["width: max-content", "overflow-wrap: break-word", "white-space: pre-wrap", "background: white"]
      if (options.font)
        styles.push(`font: ${isShorthandFontProp(options.font) ? "" : "2rem "}${options.font}`)
      let width = options.width
      if (width === false) {
        width = "auto"
        styles[2] = "white-space: pre"
      }
      if (options.html) {
        content = `<div data-lnn-koishi-html>${h("", content).toString(true)}</div>`
      } else {
        const transform = content =>
          h.transformAsync(content, {
            html: (a, c) =>
              h("div", {
                ...a,
                "data-lnn-koishi-imagify": "",
                style: "white-space:normal;font-family:initial;" + (a.style || "")
              }, c),
            message: async (a, c) => h("div", { ...a }, await transform(c)),
            p: async (a, c) => h("div", { ...a }, await transform(c)),
            at: async e =>
              h("a", { href: "#" },
                "@" + (e.type || (!session.isDirect && (await session.bot.getGuildMember(session.guildId, e.id).catch(() => null))?.nick) || (await session.bot.getUser(e.id).catch(() => null))?.name || e.name || e.id)
              ),
            face: (a, c) => h.transform(c, { img: e => h("img", { ...e, style: "height:1.25em;vertical-align:-0.25em;display:inline" }) }),
            image: e => h("img", { src: e.src || e.url, style: "max-width:20rem;height:auto;display:block" }),
            img: e => h("img", { src: e.src || e.url, style: "max-width:20rem;height:auto;display:block" }),
            video: e => h("video", { src: e.src || e.url, style: "max-width:20rem;height:auto;display:block" }),
            template: (a, c) => c,
          })
        const stringify = el => {
          if (Array.isArray(el)) return el.map(stringify).join("")
          if (el.type === "text") return h.escape(el.attrs.content)
          if (["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr", "svg", "math"].includes(el.type?.toLowerCase())) return el.toString()
          const tag = el.getTagName()
          return `<${tag}${el.toAttrString()}>${stringify(el.children)}</${tag}>`
        }
        content = stringify(await transform(content))
      }
      const html = `
        <style>
          :root { font-size: 32px }
          CSS
          ${(await ctx.lnnbotSideloadFonts?.getDeclarations()) || ""}
        </style>
      `.trim().replace(/\n\s*/g, "").replace("CSS", config.extraCSS)
      return ctx.puppeteer.render(html, async page => {
        await page.setViewport({ width: 720, height: 720, deviceScaleFactor: 1 })
        await page.evaluate(/*js*/ `
          const el = document.createElement("div")
          el.id = "myDiv"
          el.style.maxWidth = ${JSON.stringify(width)}
          el.style.padding = ${JSON.stringify(options.padding)}
          el.lang = ${JSON.stringify(options.lang || "")}
          el.innerHTML = ${JSON.stringify(content)}
          document.body.style.cssText += ${JSON.stringify(";" + styles.join(";"))}
          document.body.style.fontFamily += ${JSON.stringify(config.fallbackFonts ? "," + config.fallbackFonts : "")}
          document.body.append(el)
        `)
        await page.waitForNetworkIdle()
        await ctx.sleep(200)
        const data = await page.screenshot({
          omitBackground: true,
          clip: await (await page.$("#myDiv")).boundingBox(),
        })
        return h.image(data, "image/png")
      })
    })
  ctx.i18n.define("zh-CN", "commands.imagify", {
    description: "将内容转换成图片",
    options: {
      html: "输入 HTML",
      font: "指定字体",
      width: "指定最大宽度",
      "width.false": "不限宽度，不自动换行",
      padding: "指定边距",
      "padding.0": "不添加边距",
      lang: "指定语言代码（可能影响一些汉字的字形）",
    },
    messages: { empty: "内容为空…" },
  })
}
